package org.dreamwork.jasmine2.editor;

import com.google.gson.Gson;
import org.dreamwork.config.ApplicationConfigParser;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.jasmine2.engine.JasmineRequest;
import org.dreamwork.misc.MimeType;
import org.dreamwork.util.FileInfo;
import org.dreamwork.util.ResourceUtil;
import org.dreamwork.util.StringUtil;
import org.dreamwork.util.VirtualFile;
import org.xml.sax.SAXException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
//import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: seth.yang
 * Date: 12-7-2
 * Time: 下午4:46
 */
public class EditorResourceServlet extends HttpServlet {
    private static final Pattern p = Pattern.compile ("[\\s,|]");
//    private static final Pattern STATIC_RESOURCE = Pattern.compile ("\\.(html|js|css)$");
    private static final Map<String, String> mappings = new HashMap<String, String> ();

    private static final int BUFF_SIZE = 1024 * 5;
    public static final String MAPPING_CONTEXT_KEY = "jasmine.html.editor.mapping";
    public static final String RESOURCE_FETCHER_FACTORY = "jasmine.html.editor.resource-fetcher-factory";
    public static final String ALLOW_UPLOAD_TYPES = "allow-upload-types";

    private ResourceFetcherFactory factory;
    private IResourceFetcher fetcher;
    private String allowUploadTypes;
    private long STATIC_MODIFY_TIMESTAMP = 1;

//    private ClassLoader loader;

    @Override
    @SuppressWarnings ("unchecked")
    public void init () throws ServletException {
        super.init ();

        // load default mapping config
        URL url = getClass ().getResource ("EditorMapping.xml");
        try {
            loadMappings (url);
        } catch (Exception ex) {
            throw new ServletException (ex);
        }

        loadResourceFetcherFactory ();
        // load custom mapping config
        loadMappings ();
        // load private folder fetcher
//        loadPrivateFolderConfig ();
        ServletConfig config = getServletConfig ();
        allowUploadTypes = config.getInitParameter (ALLOW_UPLOAD_TYPES);

        try {
            File file = ResourceUtil.getPhysicalFile (EditorResourceServlet.class);
            if (file != null)
                STATIC_MODIFY_TIMESTAMP = file.lastModified ();
        } catch (Exception ex) {
            ex.printStackTrace ();
            throw new ServletException (ex);
        }
    }

    @Override
    @SuppressWarnings ("unchecked")
    protected void doPost (HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
        req.setCharacterEncoding ("utf-8");
//        ServletContext context = getServletContext ();
        String mapping = req.getServletPath ();
        if ("/uploader/create-folder".equals (mapping)) {
            createFolder (req, response);
        } else if ("/uploader/uploader".equals (mapping)) {
            JasmineRequest request = new JasmineRequest (req);
            String folderName = request.getParameter ("folderName");
            VirtualFile[] files = request.getVirtualFiles ();

//            IResourceFetcher fetcher = factory.getResourceFetcher (context);
            folderName = translatePath (folderName, request.getSession ());
            if (files != null) for (VirtualFile file : files) {
                fetcher.save (file, folderName);

                String target = request.getContextPath () + "/browser/post-upload.html";
                response.sendRedirect (target);
            }
        }
    }

    @Override
    protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding ("utf-8");
        Map<String, String> map = getPathInfo (request);
        String pathInfo = map.get ("pathInfo");
        String mapping = map.get ("mapping");

        if (pathInfo == null || pathInfo.trim ().length () == 0) {
            response.sendError (HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        MimeType type = FileInfo.getMimeType (pathInfo);
        String contentType = type != null ? type.getName () : "text/plain;charset=utf-8";
        if ("uploader".equals (mapping)) {
            response.setContentType (contentType);
            if ("image-list".equals (pathInfo)) {
                getImageList (request, response);
            } else if ("create-folder".equals (pathInfo)) {
                createFolder (request, response);
            } else if ("config".equals (pathInfo)) {
                getConfig (request.getSession (), response);
            }
        } else if ("browser".equals (mapping) || "ckeditor".equals (mapping)) {
            URL url = getResource (mapping, pathInfo);
            if (url != null) {
                response.setContentType (contentType);
                InputStream in = url.openStream ();
                dump (in, response.getOutputStream ());
            } else {
                response.sendError (HttpServletResponse.SC_NOT_FOUND);
            }
        } else {
//            IResourceFetcher fetcher = factory.getResourceFetcher (getServletContext ());
            String url = request.getRequestURI ();
            int pos = url.indexOf (mapping);
            pathInfo = url.substring (pos + mapping.length () + 1);
            pathInfo = translatePath (pathInfo, request.getSession ());
            InputStream in = fetcher.getResource (pathInfo);
            if (in == null) {
                response.sendError (HttpServletResponse.SC_NOT_FOUND);
            } else {
                response.setContentType (contentType);
                dump (in, response.getOutputStream ());
            }
        }
        response.flushBuffer ();
    }

    @Override
    protected long getLastModified (HttpServletRequest request) {
        try {
            request.setCharacterEncoding ("utf-8");
            Map<String, String> map = getPathInfo (request);
            String mapping = map.get ("mapping");
            if ("browser".equals (mapping) || "ckeditor".equals (mapping))
                return STATIC_MODIFY_TIMESTAMP;

            String resourceName = map.get ("pathInfo");
            return fetcher.getLastModified (resourceName);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace ();
            return -1;
        }
    }

    private Map<String, String> getPathInfo (HttpServletRequest request) {
        String mapping = request.getServletPath ();
        if (mapping.charAt (0) == '/') mapping = mapping.substring (1);

        String pathInfo = request.getPathInfo ();
        if (pathInfo == null || pathInfo.trim ().length () == 0) {
            String uri = request.getRequestURI ();
            String path = request.getServletPath ();
            int pos = uri.indexOf (path);
            pathInfo = uri.substring (pos + path.length ());
        }

        if (mapping.contains ("/")) {
            int pos = mapping.indexOf ('/');
            pathInfo = mapping.substring (pos + 1);
            mapping = mapping.substring (0, pos);
        }

        Map<String, String> map = new HashMap<String, String> ();
        map.put ("pathInfo", pathInfo);
        map.put ("mapping", mapping);
        return map;
    }

    private URL getResource (String key, String pathInfo) throws IOException {
        String prefix = mappings.get (key);
        if (StringUtil.isEmpty (prefix)) return null;

        ServletContext application = getServletContext ();
        String resourceName = prefix + pathInfo;
        if (resourceName.startsWith ("classpath:")) {
            resourceName = resourceName.substring ("classpath:".length ());
            return getClassPathResource (resourceName);
        } else if (resourceName.startsWith ("file:")) {
            URL url = new URL (resourceName);
            File file = new File (url.getFile ());
            return file.exists () ? file.toURI().toURL () : null;
        } else if (resourceName.startsWith ("/WEB-INF/")){
            return application.getResource (resourceName);
        } else {
            URL url = getClassPathResource (resourceName);
            if (url != null) return url;

            url = application.getResource (resourceName);
            if (url != null) return url;

            File file = new File (resourceName);
            return file.exists () ? file.toURI().toURL () : null;
        }
    }

    private URL getClassPathResource (String resourceName) {
        ClassLoader loader = getClass ().getClassLoader ();
        return loader.getResource (resourceName);
    }

    @SuppressWarnings ("unchecked")
    private void loadMappings (URL url) throws IOException, SAXException {
        ApplicationConfigParser parser = new ApplicationConfigParser (url);
        parser.addParserClass ("mappings", MappingConfigParser.class);
        parser.parse ();
        Map<String, String> urls = (Map<String, String>) parser.getValue ("mappings");
        mappings.putAll (urls);
        getServletContext ().setAttribute (MAPPING_CONTEXT_KEY, mappings);
    }

    @SuppressWarnings ("unchecked")
    private void getImageList (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//        IResourceFetcher fetcher = factory.getResourceFetcher (getServletContext ());
        String path = request.getParameter ("path");
        path = translatePath (path, request.getSession ());
        Map<String, Object> parameters = new HashMap<String, Object> ();
        for (Enumeration<String> e = request.getParameterNames (); e.hasMoreElements ();) {
            String name = e.nextElement ();
            String[] values = request.getParameterValues (name);
            if (values.length == 0) parameters.put (name, values [0]);
            else parameters.put (name, values);
        }

        String contextPath = request.getContextPath ();
        if (contextPath.charAt (0) == '/') contextPath = contextPath.substring (1);
        List<Map<String, Object>> list = fetcher.getResourceList (contextPath, path, parameters);
        Gson g = GsonHelper.getGson ();
        String result = g.toJson (list);

        response.setContentType ("text/plain;charset=utf-8");
        response.getWriter ().write (result);
        response.getWriter ().flush ();
    }

    private void createFolder (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String folderName = request.getParameter ("folderName");
//        IResourceFetcher fetcher = factory.getResourceFetcher (getServletContext ());
        folderName = translatePath (folderName, request.getSession ());
        String responseText;
        try {
            String encoded = fetcher.createFolder (folderName);
            responseText = "{status:'ok',src:'" + encoded + "'}";
        } catch (Exception ex) {
            responseText = "{status:'fail',cause:'" + ex.getMessage () + "'}";
        }
        response.getWriter ().write (responseText);
        response.getWriter ().flush ();
    }

    private void loadMappings () throws ServletException {
        ServletConfig config = getServletConfig ();
        ServletContext application = getServletContext ();
        ClassLoader loader = getClass ().getClassLoader ();
        String value = config.getInitParameter ("mapping");
        try {
            if (value != null) {
                String[] a = p.split (value);
                for (String s : a) {
                    if (s != null && s.trim ().length () > 0) {
                        s = s.trim ();
                        if (s.startsWith ("classpath:")) {
                            s = s.substring ("classpath:".length ());
                            if (s.charAt (0) == '/') s = s.substring (1);
                            loadMappings (loader.getResource (s));
                        } else if (s.charAt (0) == '/') {
                            loadMappings (application.getResource (s));
                        } else if (s.startsWith ("file:")) {
                            loadMappings (new URL (s));
                        }
                    }
                }
            } else {
                URL url = application.getResource ("/WEB-INF/mapping.xml");
                loadMappings (url);
            }
            application.setAttribute (MAPPING_CONTEXT_KEY, mappings);
        } catch (Exception ex) {
            throw new ServletException (ex);
        }
    }

/*
    @SuppressWarnings ("unchecked")
    private void loadPrivateFolderConfig () {
        ServletConfig config = getServletConfig ();
        ServletContext application = getServletContext ();
        ClassLoader loader = getClass ().getClassLoader ();
        String supported = config.getInitParameter ("private-folder-supported");
        if (Boolean.valueOf (supported)) {
            String className = config.getInitParameter ("private-folder-fetcher");
            if (!StringUtil.isEmpty (className)) {
                className = className.trim ();
                try {
                    Class<IPrivateFolderFetcher> clazz = (Class<IPrivateFolderFetcher>) loader.loadClass (className);
                    IPrivateFolderFetcher fetcher = clazz.newInstance ();
                    application.setAttribute (RESOURCE_FETCHER_FACTORY, fetcher);
                } catch (Exception ex) {
                    ex.printStackTrace ();
                }
            }
        }
    }
*/

    private String translatePath (String path, HttpSession session) throws ServletException, IOException {
        if (path.charAt (0) == '/') path = path.substring (1);
        String[] array = path.split ("/");
        if (array.length == 0) return path;

//        IResourceFetcher fetcher = factory.getResourceFetcher (getServletContext ());
        if (fetcher == null) {
            throw new ServletException ("");
        }

        if ("private".equalsIgnoreCase (array [0])) {
            array [0] = fetcher.getPrivateFolderName (session);
        } else if ("public".equalsIgnoreCase (array [0])) {
            array [0] = fetcher.getPublicFolderName ();
        }
        StringBuilder builder = new StringBuilder ();
        for (String part : array) {
            if (builder.length () > 0) builder.append ('/');
            builder.append (part);
        }
        return builder.toString ();
    }

    private void loadResourceFetcherFactory () {
        ServletConfig config = getServletConfig ();
        ServletContext context = getServletContext ();

        String className = config.getInitParameter ("resource-fetcher-factory");
        if (StringUtil.isEmpty (className))
            throw new RuntimeException ("ResourceFetcherFactory can't be null");

        try {
            @SuppressWarnings ("unchecked")
            Class<? extends ResourceFetcherFactory> type = (Class<? extends ResourceFetcherFactory>) Class.forName (className);
            factory = type.newInstance ();
            fetcher = factory.getResourceFetcher (context);
            context.setAttribute (RESOURCE_FETCHER_FACTORY, factory);
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }

    private void dump (InputStream in, OutputStream out) throws IOException {
        try {
            int length = Math.min (in.available (), BUFF_SIZE);
            length = length == 0 ? BUFF_SIZE : length;
            byte[] buff = new byte [length];
            while ((length = in.read (buff)) != -1) {
                out.write (buff, 0, length);
                out.flush ();
            }
        } finally {
            in.close ();
        }
    }

    private void getConfig (HttpSession session, HttpServletResponse response) throws IOException {
//        IResourceFetcher fetcher = factory.getResourceFetcher (getServletContext ());
        Map<String, Object> map = new HashMap<String, Object> ();
        map.put ("isPublicSupported", fetcher.isPublicFolderSupported (session));
        map.put ("canCreateFolder", fetcher.canCreateFolder (session));
        map.put ("canRemoveItems", fetcher.canRemoveItem (session));
        map.put ("selectionMode", fetcher.getSelectionMode () == IResourceFetcher.SELECTION_MODE_SINGLE ? "single" : "multiple");
        if (!StringUtil.isEmpty (allowUploadTypes)) {
            map.put ("allowUploadTypes", allowUploadTypes);
        }

        Gson g = GsonHelper.getGson ();
        response.setContentType ("text/plain;charset=utf-8");
        response.getWriter ().write (g.toJson (map));
    }
}